﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using PrimitiveEngine.Primitives;

namespace PrimitiveEngine
{
    public class PrimitiveBatch
    {
        public Matrix Projection { get; set; }
        public Matrix View { get; set; }

        private List<PEPrimitive> opaquePrimitives;
        private List<PEPrimitive> transparentPrimitives;
        private GraphicsDevice graphicsDevice;
        private VertexDeclaration vertexDeclarationTexture;
        private BasicEffect effect;

        private VertexPositionColorTexture[] vertices;
        private int vertexCount;

        private short[] indices;
        private int indiceCount;

        private int primitiveCount;
        private bool beginCalled;

        private int totalTriangleCount;
        private int totalVertexCount;
        private int totalDrawCalls;
        private int totalOpaquePrimitivesRendered;
        private int totalTransparentPrimitivesRendered;
        private TimeSpan totalRenderTime;
        private Stopwatch watch;

        public int TriangleCount { get { return totalTriangleCount; } }
        public int VertexCount { get { return totalVertexCount; } }
        public int DrawCalls { get { return totalDrawCalls; } }
        public int OpaquePrimitiveCount { get { return totalOpaquePrimitivesRendered; } }
        public int TransparentPrimitiveCount { get { return totalTransparentPrimitivesRendered; } }
        public TimeSpan RenderTime { get { return totalRenderTime; } }

        public PrimitiveBatch(GraphicsDevice gd)
        {
            graphicsDevice = gd;
            vertexDeclarationTexture = new VertexDeclaration(graphicsDevice, VertexPositionColorTexture.VertexElements);
            effect = new BasicEffect(graphicsDevice, null);
            effect.VertexColorEnabled = true;

            opaquePrimitives = new List<PEPrimitive>();
            transparentPrimitives = new List<PEPrimitive>();

            vertices = new VertexPositionColorTexture[33000];
            indices = new short[11000];

            watch = new Stopwatch();
        }

        public void Begin()
        {
            Begin(Matrix.CreateOrthographicOffCenter(0, graphicsDevice.Viewport.Width,
                    graphicsDevice.Viewport.Height, 0, 0, 1), Matrix.Identity);
        }

        public void Begin(Matrix projectionMatrix, Matrix viewMatrix)
        {
            Projection = projectionMatrix;
            View = viewMatrix;
            
            opaquePrimitives.Clear();
            transparentPrimitives.Clear();
            vertexCount = 0;
            indiceCount = 0;
            primitiveCount = 0;
            totalTriangleCount = 0;
            totalVertexCount = 0;
            totalDrawCalls = 0;
            totalOpaquePrimitivesRendered = 0;
            totalTransparentPrimitivesRendered = 0;
            beginCalled = true;
        }

        public void End()
        {
            watch.Start();

            if (!beginCalled)
                throw new Exception("You must call Begin() before End()");


            effect.Parameters["World"].SetValue(Matrix.Identity);
            effect.Parameters["View"].SetValue(View);
            effect.Parameters["Projection"].SetValue(Projection);

            // traverse opaque primitive list...no sorting needed as depth buffer is used
            for (int i = 0; i < opaquePrimitives.Count; i++)
            {
                opaquePrimitives[i].Draw(ref vertices, ref vertexCount, ref indices, ref indiceCount);
                primitiveCount += opaquePrimitives[i].PrimitiveCount;

                if (primitiveCount > 3000)
                    RenderOpaque();
            }

            RenderOpaque();

            // current method requires transparent primitives to be sorted prior to building
            // the vertice rendering list
            //transparentPrimitives.Sort();

            // traverse transparent primitive list and add values
            for (int i = 0; i < transparentPrimitives.Count; i++)
            {
                transparentPrimitives[i].Draw(ref vertices, ref vertexCount, ref indices, ref indiceCount);
                primitiveCount += transparentPrimitives[i].PrimitiveCount;

                if (primitiveCount > 3000)
                    RenderTransparent();
            }

            RenderTransparent();


            totalOpaquePrimitivesRendered = opaquePrimitives.Count;
            totalTransparentPrimitivesRendered = transparentPrimitives.Count;

            beginCalled = false;

            watch.Stop();
            totalRenderTime = watch.Elapsed;
            watch.Reset();
        }

        internal void RenderTransparent()
        {
            // don't render anything if nothing has been added to the list
            if (primitiveCount > 0)
            {
                // we are rendering transparent primitives so we do not write to the depth buffer
                //graphicsDevice.RenderState.DepthBufferWriteEnable = false;
                graphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

                effect.Begin();
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Begin();

                    totalDrawCalls++;
                    graphicsDevice.DrawUserIndexedPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, vertices, 0, vertexCount, indices, 0, primitiveCount);

                    pass.End();
                }
                effect.End();

                graphicsDevice.RenderState.DepthBufferWriteEnable = true;
            }

            totalVertexCount += vertexCount;
            vertexCount = 0;
            indiceCount = 0;
            totalTriangleCount += primitiveCount;
            primitiveCount = 0;
        }

        internal void RenderOpaque()
        {
            graphicsDevice.VertexDeclaration = vertexDeclarationTexture;
            graphicsDevice.RenderState.CullMode = CullMode.None;
            graphicsDevice.RenderState.DepthBufferEnable = true;
            graphicsDevice.RenderState.AlphaBlendEnable = true;
            graphicsDevice.RenderState.AlphaSourceBlend = Blend.SourceAlpha;

            // don't render anything if nothing has been added to the list
            if (primitiveCount > 0)
            {
                effect.Begin();
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Begin();

                    totalDrawCalls++;
                    graphicsDevice.DrawUserIndexedPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, vertices, 0, vertexCount, indices, 0, primitiveCount);

                    pass.End();
                }
                effect.End();
            }

            totalVertexCount += vertexCount;
            vertexCount = 0;
            indiceCount = 0;
            totalTriangleCount += primitiveCount;
            primitiveCount = 0;
        }

        public void DrawLine(PELine line)
        {
            if (line.BorderColor.A == 255 && line.FillColor.A == 255)
                opaquePrimitives.Add(line);
            else
                transparentPrimitives.Add(line);
        }

        public void DrawCircle(PECircle circle)
        {
            if (circle.BorderColor.A == 255 && circle.FillColor.A == 255)
                opaquePrimitives.Add(circle);
            else
                transparentPrimitives.Add(circle);
        }

        public void DrawEllipse(PEEllipse ellipse)
        {
            if (ellipse.BorderColor.A == 255 && ellipse.FillColor.A == 255)
                opaquePrimitives.Add(ellipse);
            else
                transparentPrimitives.Add(ellipse);
        }

        public void DrawRectangle(PERectangle rectangle)
        {
            if (rectangle.BorderColor.A == 255 && rectangle.FillColor.A == 255)
                opaquePrimitives.Add(rectangle);
            else
                transparentPrimitives.Add(rectangle);
        }
        
        public void DrawPolygon(PEPolygon poly)
        {
            if (poly.BorderColor.A == 255 && poly.FillColor.A == 255)
                opaquePrimitives.Add(poly);
            else
                transparentPrimitives.Add(poly);
        }
         
    }
}
